package kubernetescli

import (
	"bytes"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"html/template"
	"log"
	"strconv"
	"strings"
	"time"

	"github.com/Kindling-project/chaos-backend/api"
	"github.com/Kindling-project/chaos-backend/tools/wrapper"
	"github.com/chaos-mesh/chaos-mesh/api/v1alpha1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/runtime/serializer/yaml"
)

// 基础的故障类型,支持通过CRD的yaml定义故障类型
type UnstructuredChaos struct {
	ChaosKind v1alpha1.TemplateType
	GVR       schema.GroupVersionResource
	// 故障定义
	ChaosSpecTemplate map[string]any
	// Resource
	Namespace string

	WithAutoPodSelector bool
}

var emptyList = unstructured.UnstructuredList{}

func (sc *UnstructuredChaos) List() (interface{}, error) {
	if dynamicClient == nil {
		return nil, errors.New("dynamicClient not initialized yet")
	}
	resourceList, err := dynamicClient.Resource(sc.GVR).Namespace(sc.Namespace).List(context.TODO(), metav1.ListOptions{})

	if resourceList == nil {
		return emptyList, err
	}

	return *resourceList, err
}

// deepCopySpec 通过json序列化完成Spec模板深拷贝
func deepCopySpec(origin map[string]interface{}) (map[string]interface{}, error) {
	originalJSON, err := json.Marshal(origin)
	if err != nil {
		return nil, err
	}
	var newMap map[string]interface{}
	err = json.Unmarshal(originalJSON, &newMap)
	if err != nil {
		return nil, err
	}
	return newMap, nil
}

// Create 创建故障并返回生成的CRD对象
//
// 对于SpecifiedChaos故障类型,不在代码中直接定义故障的具体结构,而是使用unstructured结构解析spec_template
// 同时返回结构也直接使用*unstructured.Unstructured
func (sc *UnstructuredChaos) Create(vo *api.Chaos) (interface{}, error) {
	if dynamicClient == nil {
		return nil, errors.New("dynamicClient not initialized yet")
	}
	spec, err := deepCopySpec(sc.ChaosSpecTemplate)
	if err != nil {
		return nil, fmt.Errorf("failed to create chaos when copy spec: %v", err)
	}

	// 更新元数据
	resource, err := sc.newResource(vo, spec)
	if err != nil || resource == nil {
		return nil, fmt.Errorf("failed to create chaos when create resource: %v", err)
	}
	res, err := dynamicClient.Resource(sc.GVR).Namespace(sc.Namespace).Create(context.TODO(), resource, metav1.CreateOptions{})

	if res == nil {
		return emptyList, err
	}

	return *res, err
}

func (sc *UnstructuredChaos) DeleteAll() (interface{}, error) {
	if dynamicClient == nil {
		return nil, errors.New("dynamicClient not initialized yet")
	}
	resourceList, err := dynamicClient.Resource(sc.GVR).List(context.TODO(), metav1.ListOptions{})
	if err != nil {
		return nil, err
	}

	var lastObj *unstructured.Unstructured
	for _, item := range resourceList.Items {
		lastObj = &item
		err = dynamicClient.Resource(sc.GVR).Namespace(sc.Namespace).Delete(context.TODO(), item.GetName(), metav1.DeleteOptions{})
		if err != nil {
			return nil, err
		}
	}

	if lastObj == nil {
		return emptyList, err
	}

	return *lastObj, nil
}

func (sc *UnstructuredChaos) ToChaosVO(obj *interface{}) []api.Chaos {
	if obj == nil {
		return nil
	}

	var result []api.Chaos = make([]api.Chaos, 0)
	switch obj := (*obj).(type) {
	case unstructured.Unstructured:
		result = append(result, wrapper.UnstructuredChaosToVO(&obj))
	case unstructured.UnstructuredList:
		for _, item := range obj.Items {
			result = append(result, wrapper.UnstructuredChaosToVO(&item))
		}
	default:
		log.Printf("Unexpected Input for UnstructuredChaos: %+v", obj)
	}

	return result
}

var chaosTemplate *template.Template

func SetupChaosTemplate(filePath string) error {
	var err error
	if len(filePath) == 0 {
		chaosTemplate, err = template.New("chaos").ParseFiles("unstructured_chaos.gotmpl")
	} else {
		chaosTemplate, err = template.New("chaos").ParseFiles(filePath)
	}
	return err
}

func (sc *UnstructuredChaos) newResource(vo *api.Chaos, spec map[string]any) (*unstructured.Unstructured, error) {
	var paramMap = make(map[string]interface{})
	for _, param := range vo.Params {
		paramMap[param.Name] = param.Value
	}

	if sc.WithAutoPodSelector {
		paramMap["AutoPodSelector"] = true
	}
	ts := time.Now().Unix()
	paramMap["Name"] = strings.ToLower(fmt.Sprintf(`%s-%s`, vo.ChaosType, strconv.FormatInt(ts, 10)))
	paramMap["Namespace"] = sc.Namespace
	data := struct {
		*api.Chaos
		Params map[string]interface{}
	}{
		Chaos:  vo,
		Params: paramMap,
	}

	var resultBuffer bytes.Buffer
	err := chaosTemplate.ExecuteTemplate(&resultBuffer, "unstructured_chaos.gotmpl", data)
	if err != nil {
		return nil, err
	}
	dec := yaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme)
	obj := &unstructured.Unstructured{}
	_, _, err = dec.Decode(resultBuffer.Bytes(), nil, obj)

	specMap := obj.Object["spec"].(map[string]any)
	for k, v := range spec {
		specMap[k] = v
	}
	return obj, err
}
